<?php

namespace Formax;

use
    Phalcon\Http\Request\File as RequestFile,
    Formax\Uploader\Result as UploaderResult,
    Formax\Uploader\Exception as UploaderException,
    Formax\Uploader\RenameInterface as RenameInterface;

defined('UPLOAD_ERR_SIZE')    || define('UPLOAD_ERR_SIZE', 10);
defined('UPLOAD_ERR_TYPE')    || define('UPLOAD_ERR_TYPE', 11);
defined('UPLOAD_ERR_EXISTS')  || define('UPLOAD_ERR_EXISTS', 12);
defined('UPLOAD_ERR_SAVE')    || define('UPLOAD_ERR_SAVE', 13);
defined('UPLOAD_ERR_RENAME')  || define('UPLOAD_ERR_RENAME', 14);
defined('UPLOAD_ERR_UNKNOWN') || define('UPLOAD_ERR_UNKNOWN', 99);

/**
 * 多文件上传
 *     $uploader = \Formax\Uploader::factory($config);
 *     foreach (service('request')->getUploadedFiles() as $file) {
 *         $result = $uploader->save($file, function($path, $file) use $file {
 *             return false | $newfile;
 *         });
 *     }
 *
 * 单文件上传
 *     $uploader = \Formax\Uploader::factory($config);
 *     $uploader->save(get_uploaded_file($key), new \Formax\Uploader\Rename\Md5);
 *     $uploader->save(get_uploaded_file($key), function ($path, $file) {
 *         return false | $newfile;
 *     });
 */
class Uploader
{

    /**
     * 文件上传路径
     *
     * @var string
     */
    protected $_path = DOC_PATH;

    /**
     * 允许上传的文件类型，默认全部允许
     *
     * @var integer
     */
    protected $_types = null;

    /**
     * 允许上传的最大文件大小
     *
     * @var integer
     */
    protected $_maxSize = 0; // (kb)

    /**
     * 是否覆盖已存在的文件
     *
     * @var boolean
     */
    protected $_overwrite = false;

    /**
     * 上传自动重命名类
     *
     * @var \Formax\Uploader\Rename\Interface
     */
    protected $_rename = null;

    /**
     * 上传错误代码
     *
     * @var integer
     */
    protected $_errno = array();

    /**
     * 上传结果
     *
     * @var \Formax\Uploader\Result
     */
    protected $_result = array();

    /**
     * 上传错误代码对应消息
     */
    protected $_errors = array(
        UPLOAD_ERR_OK         => '文件上传成功',
        UPLOAD_ERR_INI_SIZE   => '上传的文件大小超出服务器限定的值',
        UPLOAD_ERR_FORM_SIZE  => '文件大小超出 HTML 控件限定的值',
        UPLOAD_ERR_PARTIAL    => '文件只有部分被上传',
        UPLOAD_ERR_NO_FILE    => '没有文件被上传',
        UPLOAD_ERR_NO_TMP_DIR => '上传失败，原因未知',
        UPLOAD_ERR_NO_TMP_DIR => '找不到临时文件夹',
        UPLOAD_ERR_CANT_WRITE => '文件写入失败',
        UPLOAD_ERR_SIZE       => '上传的文件超出限定大小',
        UPLOAD_ERR_TYPE       => '不允许上传的文件类型',
        UPLOAD_ERR_EXISTS     => '文件已存在，不允许覆盖保存',
        UPLOAD_ERR_SAVE       => '文件保存失败',
        UPLOAD_ERR_RENAME     => '文件重名名时出错',
        UPLOAD_ERR_UNKNOWN    => '未知上传错误',
    );

    /**
     * 工厂方法实例化上传类
     *
     * @param array $config
     */
    public static function factory(array $config = null)
    {
        return new self($config);
    }

    /**
     * 构造方法
     *
     * @param array $config
     */
    public function __construct(array $config = null)
    {
        foreach ($config as $key => $value) {
            $method = camel("set{$key}");
            if (method_exists($this, $method)) {
                $this->$method($value);
            }
        }
    }

    /**
     * 设置上传路径
     *
     * @param  string                     $path
     * @param  boolean                    $mkdir
     * @return \Formax\Uploader
     * @throws \Formax\Uploader\Exception
     */
    public function setPath($path, $mkdir = true)
    {
        if (! is_dir($path)) {
            if ($mkdir) {
                if (! mkdir($path, 0777, true)) {
                    throw new UploaderException('创建文件上传目录失败');
                }
            } else {
                throw new UploaderException('无效的文件上传目录');
            }
        }

        $this->_path = realpath($path);

        return $this;
    }

    /**
     * 获取上传路径
     *
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }

    /**
     * 设置允许上传的文件类型
     *
     * @param  string|array     $types
     * @return \Formax\Uploader
     */
    public function setTypes($types)
    {
        $this->_types = array_map('strtolower', is_array($types) ? $types : array($types));

        return $this;
    }

    /**
     * 获取允许上传的文件类型
     *
     * @return array
     */
    public function getTypes()
    {
        return $this->_types;
    }

    /**
     * 设置允许上传的最大文件大小
     *
     * @param  integer          $maxSize
     * @return \Formax\Uploader
     */
    public function setMaxSize($maxSize)
    {
        $this->_maxSize = (integer) $maxSize;

        return $this;
    }

    /**
     * 获取允许上传的最大文件大小
     *
     * @return integer
     */
    public function getMaxSize()
    {
        return $this->_maxSize;
    }

    /**
     * 设置是否覆盖已存在的文件
     *
     * @param  boolean          $overwrite
     * @return \Formax\Uploader
     */
    public function setOverwrite($overwrite)
    {
        $this->_overwrite = (boolean) $overwrite;

        return $this;
    }

    /**
     * 设置文件自动重命名类
     *
     * @param  \Formax\Uploader\RenameInterface $rename
     * @return \Formax\Uploader
     */
    public function setRename(RenameInterface $rename)
    {
        $this->_rename = $rename;

        return $this;
    }

    /**
     * 保存文件
     *
     * @param  \Phalcon\Http\Request\File $file
     * @return boolean
     */
    public function save(RequestFile $file)
    {
        $key = $file->getKey();

        // 设置默认结果
        $this->_errno[$key] = $file->getError();

        // 检测是否存在上传错误
        if ($file->getError() != UPLOAD_ERR_OK) {
            return $this->_error($key, $file->getError());
        }

        // 检测文件大小
        if ($this->_maxSize > 0 && $file->getSize() > $this->_maxSize * 1024) {
            return $this->_error($key, UPLOAD_ERR_SIZE);
        }

        // 检测文件类型
        if (! empty($this->_types)) {
            $type = strtolower($file->getRealType() ? $file->getRealType() : $file->getType());
            if (! in_array($type, $this->_types)) {
                return $this->_error($key, UPLOAD_ERR_TYPE);
            }
        }

        // 存储文件名
        $savefile = $this->_path . '/' . $file->getName();
        if (is_file($savefile) && ! $this->_overwrite) {
            return $this->_error($key, UPLOAD_ERR_EXISTS);
        }

        // 保存上传文件
        if (! $file->moveto($savefile)) {
            return $this->_error($key, UPLOAD_ERR_SAVE);
        }

        $this->_result[$key] = new UploaderResult($savefile);

        // 启用重命名
        if ($this->_rename instanceof RenameInterface) {
            if ($savefile = $this->_rename->rename($this->_path, $savefile)) {
                $this->_result[$key] = new UploaderResult($savefile);
            } else {
                @unlink($savefile); // 删除临时文件
                return $this->_error($key, UPLOAD_ERR_RENAME);
            }
        }

        return true;
    }

    /**
     * 返回上传结果
     *
     * @param  string $key
     * @return mixed
     */
    public function getResult($key = null)
    {
        return is_null($key) ? $this->_result : array_get($this->_result, $key);
    }

    /**
     * 返回错误
     *
     * @param  string  $key
     * @param  integer $errno
     * @return false
     */
    protected function _error($key, $errno)
    {
        $this->_errno[$key] = $errno;

        return false;
    }

    /**
     * 获取错误代码
     *
     * @param  string  $key
     * @return integer
     */
    public function getErrno($key)
    {
        return isset($this->_errno[$key]) ? $this->_errno[$key] : UPLOAD_ERR_UNKNOWN;
    }

    /**
     * 获取错误消息
     *
     * @param  string $key
     * @return string
     */
    public function getErrorMessage($key)
    {
        $errno = $this->getErrno($key);

        return array_get($this->_errors, $errno, $this->_errors[UPLOAD_ERR_UNKNOWN]);
    }

}
